# Определение методов

Синтаксис простого метода выглядит следующим образом:

```dotty
def methodName(param1: Type1, param2: Type2): ReturnType =
  // здесь тело метода
end methodName   // опционально, можно не указывать
```

В этом синтаксисе:

- ключевое слово `def` используется для определения метода
- для наименования методов согласно стандартам Scala используется camel case convention
- у параметров метода необходимо всегда указывать тип
- возвращаемый тип метода указывать необязательно
- методы могут состоять как только из одной строки, так и из нескольких строк
- метку окончания метода `end methodName` указывать необязательно, её рекомендуется указывать только для длинных методов

Вот два примера однострочного метода с именем `add`, который принимает два входных параметра `Int`. 
Первая версия явно показывает возвращаемый тип метода - `Int`, а вторая - нет:

```dotty
def add(a: Int, b: Int): Int = a + b
def add(a: Int, b: Int) = a + b
```

У публичных методов рекомендуется всегда указывать тип возвращаемого значения.
Объявление возвращаемого типа может упростить его понимание при просмотре кода другого человека 
или своего кода спустя некоторое время.

### Вызов методов

Вызов методов прост:

```dotty
val x = add(1, 2)
```

Коллекции Scala имеют десятки встроенных методов. Эти примеры показывают, как их вызывать:

```dotty
val x = List(1, 2, 3)
// x: List[Int] = List(1, 2, 3)
x.size
// res0: Int = 3
x.contains(1)
// res1: Boolean = true
x.map(_ * 10)
// res2: List[Int] = List(10, 20, 30)
```

Внимание:

- `size` не принимает аргументов и возвращает количество элементов в списке 
- метод `contains` принимает один аргумент — значение для поиска
- `map` принимает один аргумент - функцию; в данном случае в него передается анонимная функция

### Многострочные методы

Если метод длиннее одной строки, начинайте тело метода со второй строки с отступом вправо:

```dotty
def addThenDouble(a: Int, b: Int): Int =
  val sum = a + b
  sum * 2
addThenDouble(1, 1)  
// res3: Int = 4
```

В этом методе:

- `sum` — неизменяемая локальная переменная; к ней нельзя получить доступ вне метода
- последняя строка удваивает значение `sum` - именно это значение возвращается из метода

Обратите внимание, что нет необходимости в операторе `return` в конце метода. 
Поскольку почти все в Scala является выражением — то это означает, что каждая строка кода возвращает 
(или вычисляет) значение — нет необходимости использовать `return`.

Это видно на примере того же метода, но в более сжатой форме:

```dotty
def addThenDouble(a: Int, b: Int): Int = (a + b) * 2
```

В теле метода можно использовать все возможности Scala: 

- `if`/`else` выражения 
- `match` выражения 
- циклы `while`
- циклы `for` и `for` выражения
- присвоение переменных 
- вызовы других методов 
- определения других методов

В качестве ещё одного примера многострочного метода, 
`getStackTraceAsString` преобразует свой входной параметр `Throwable` в правильно отформатированную строку:

```dotty
def getStackTraceAsString(t: Throwable): String =
  val sw = StringWriter()
  t.printStackTrace(PrintWriter(sw))
  sw.toString
```

В этом методе:

- в первой строке переменная `sw` принимает значение нового экземпляра `StringWriter`
- вторая строка сохраняет содержимое трассировки стека в `StringWriter`
- третья строка возвращает строковое представление трассировки стека

### Рекомендации о методах, которые не принимают параметров

Когда метод не принимает параметров, говорят, что он имеет _arity_ уровень 0 (_arity-0_). 
Аналогично, если метод принимает один параметр - это метод с _arity-1_. 

Когда создаются методы _arity-0_:

- если метод выполняет побочные эффекты, такие как вызов `println`, метод объявляется с пустыми скобками.
- если метод не выполняет побочных эффектов, например, получение размера коллекции, 
что аналогично доступу к полю в коллекции, круглые скобки опускаются.

Например, этот метод выполняет побочный эффект, поэтому он объявлен с пустыми скобками:

```dotty
def speak() = println("hi")
```

При вызове метода нужно обязательно указывать круглые скобки, если он был объявлен с ними:

```dotty
speak     // error: "method speak must be called with () argument"
speak()   // prints "hi"
```

Хотя это всего лишь соглашение, его соблюдение значительно улучшает читаемость кода: 
с первого взгляда становится понятно, что метод с _arity-0_ имеет побочные эффекты.

### Использование if в качестве тела метода

Поскольку выражения `if`/`else` возвращают значение, их можно использовать в качестве тела метода. 
Вот метод с именем `isTruthy`, реализующий Perl-определения `true` и `false`:

```dotty
def isTruthy(a: Any) =
  if a == 0 || a == "" || a == false then
    false
  else
    true
```

Примеры показывают, как работает метод:

```dotty
isTruthy(0)   
isTruthy("")  
isTruthy("hi")
isTruthy(1.0) 
```

### Использование match в качестве тела метода

Довольно часто в качестве тела метода используются match-выражения.
Вот еще одна версия `isTruthy`, написанная с `match` выражением:

```dotty
def isTruthy(a: Matchable) = a match
  case 0 | "" | false => false
  case _ => true
```

Этот метод работает точно так же, как и предыдущий, в котором использовалось выражение `if`/`else`. 
Вместо `Any` в качестве типа параметра используется `Matchable`, 
чтобы принять любое значение, поддерживающее сопоставление с образцом (pattern matching).

### Контроль видимости методов в классах

В классах, объектах, `trait`-ах и `enum`-ах методы Scala по умолчанию общедоступны, 
поэтому созданный здесь экземпляр `Dog` может получить доступ к методу `speak`:

```dotty
class Dog:
  def speak() = println("Woof")
val d = new Dog
d.speak()
// Woof
```

Также методы можно помечать как `private`. 
Это делает их закрытыми в текущем классе, поэтому их нельзя вызвать или переопределить в подклассах:

```dotty
class Animal:
  private def breathe() = println("I’m breathing")
  
class Cat extends Animal:
  // this method won’t compile
  override def breathe() = println("Yo, I’m totally breathing")
```

Если необходимо сделать метод закрытым в текущем классе, но разрешить подклассам вызывать или переопределять его,
метод помечается как `protected`, как показано в примере с методом `speak`:

```dotty
class Animal:
  private def breathe() = println("I’m breathing")
  def walk() =
    breathe()
    println("I’m walking")
  protected def speak() = println("Hello?")

class Cat extends Animal:
  override def speak() = println("Meow")

val cat = new Cat
cat.walk()
cat.speak()
cat.breathe()   // won’t compile because it’s private
```

Настройка `protected` означает:

- к методу (или полю) могут обращаться другие экземпляры того же класса
- метод (или поле) не виден в текущем пакете
- он доступен для подклассов

### Методы в объектах

Ранее было показано, что `trait`-ы и классы могут иметь методы. 
Ключевое слово `object` используется для создания одноэлементного класса, и `object` также может содержать методы. 
Это хороший способ сгруппировать набор "служебных" методов. 
Например, этот объект содержит набор методов, которые работают со строками:

```dotty
object StringUtils:

  def truncate(s: String, length: Int): String = s.take(length)

  def lettersAndNumbersOnly_?(s: String): Boolean =
    s.matches("[a-zA-Z0-9]+")

  def containsWhitespace(s: String): Boolean =
    s.matches(".*\\s.*")

end StringUtils
```

### Вызов методов суперкласса

Методы суперкласса можно вызвать через ключевое слово `super.`, как показано в примере:

```dotty
class Animal:
  def speak() = println("Hello?")

class Cat extends Animal:
  override def speak() =
    super.speak()
    println("Meow")

val cat = new Cat
cat.speak()
// Hello?
// Meow
```


---

**Ссылки:**

- [Scala3 book](https://docs.scala-lang.org/scala3/book/taste-methods.html)
- [Scala3 book, Method Features](https://docs.scala-lang.org/scala3/book/methods-most.html)
